package ate;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.File;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.net.URISyntaxException;


/**
 * 
 * This class provides a means of using Unix netlink socket
 * connections in Java
 * 
 * @author Klaus Trainer
 */
public class UnixNetlinkSocket {

    private static File jarFile;
    static {
        // Load the Unix netlink socket C library
        getJarPath();
        try {
            loadNativeLib();
        } catch (IOException ioe) {
            throwLink(ioe);
        }
    }

    private static void getJarPath() {
        try {
            jarFile = new File(JNL.jarURL.toURI());
        } catch(URISyntaxException e) {
            throwLink(e);
        }
    }

    private static void loadNativeLib() throws IOException {
        File lib = getNativeLibTarget();
        if(!lib.exists() || jarNewer(lib)) {
            try {
                extractNativeLib(lib);
                if(!lib.exists()) {
                    throwLink("The native library was not extracted");
                }
            } catch(IOException e) {
                throwLink(e);
            } catch(URISyntaxException e) {
                throwLink(e);
            }
        }

        String path = "";
        try {
            path = lib.getCanonicalPath();
        } catch(IOException e) {
            throwLink(e);
        }
        System.load(path); 
    }

    private static Boolean jarNewer(File lib) {
        return lib.lastModified() < jarFile.lastModified();
    }
    
    private static void throwLink(Throwable e) {
        throwLink(e.toString());
    }
    private static void throwLink(String s) {
        throw new UnsatisfiedLinkError(s);
    }

    private static File getNativeLibTarget() throws IOException {
        String p = platform();
        String ext = "darwin".equals(p) ? "dylib" : "so";

        File tmpdir = File.createTempFile("jnl-temp", Long.toString(System.nanoTime()));

        if(!(tmpdir.delete())) {
            throw new IOException("Could not delete temp file: " + tmpdir.getAbsolutePath());
        }

        if(!(tmpdir.mkdir())) {
            throw new IOException("Could not create temp directory: " + tmpdir.getAbsolutePath());
        }

        tmpdir.deleteOnExit();

        String path = String.format(tmpdir.getAbsolutePath() + "/libunixnetlinksocket-%s-%s.%s", p, arch(), ext);
        File lib = new File(path);
        lib.deleteOnExit();
        return lib;
    }

    private static void extractNativeLib(File target) 
        throws IOException, URISyntaxException {

        JarFile jar = new JarFile(jarFile);
        ZipEntry z = jar.getEntry(target.getName());
        if(z == null) {
            throwLink("Could not find library: "+target.getName());
        }

        InputStream in = jar.getInputStream(z);
        try {
            OutputStream out = new BufferedOutputStream(
                new FileOutputStream(target));
            try {
                byte[] buf = new byte[2048];
                for(;;) {
                    int n = in.read(buf);
                    if(n < 0) {
                        break;
                    }
                    out.write(buf, 0, n);
                }
            } finally {
                out.close();
            }
        } finally {
            in.close();
        }
    }

    private static String arch() {
        String a = System.getProperty("os.arch");
        if("amd64".equals(a) || "x86_64".equals(a)) {
            return "x86_64";
        }
        return a;
    }

    private static String platform() {
        String p = System.getProperty("os.name").toLowerCase();
        if("mac os x".equals(p)) {
            return "darwin";
        }
        return p;
    }

    protected int nativeSocketFileHandle;

    protected int socketType=JNL.SOCK_RAW;

    private int timeout;

    protected native static int nativeOpen();
        
    protected native static byte[] nativeReadevent(int nativeSocketFileHandle);

    protected native static int nativeClose(int nativeSocketFileHandle);

    protected UnixNetlinkSocketReadThread thread;

    private INLMessageProcessor processor;

    public UnixNetlinkSocket(INLMessageProcessor processor)
    {
     this.processor=processor;
     if ((nativeSocketFileHandle = nativeOpen()) == -1)
            throw new Error("Unable to open Unix Netlink socket");
     thread= new UnixNetlinkSocketReadThread();
     thread.start();
    }
    
    /**
     * Sets the read timeout for the socket. If a read call blocks for the
     * specified amount of time it will be cancelled, and a
     * java.io.InterruptedIOException will be thrown. A <code>timeout</code>
     * of zero is interpreted as an infinite timeout.
     * 
     * @param timeout
     *            The specified timeout, in milliseconds.
     */
    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    public int getTimeout() {
        return timeout;
    }

    /**
     * Closes the socket
     */
    public void close() {
    	  thread.quit();
        nativeClose(nativeSocketFileHandle);
    }


    protected class UnixNetlinkSocketReadThread extends Thread {
        private int count, off=0, len=1;
				private boolean quit=false;
				
        private byte[] b=new byte[1024];

        public UnixNetlinkSocketReadThread() {
        }
        
	public void quit(){
		quit=true;
	}
				
        public void run() {
            while(!quit){
                byte[] rt = nativeReadevent(nativeSocketFileHandle);
  		processor.processor(rt);
            }	
        }

        public int getData() {
            return count;
        }
    }
}
